package com.andova.glcamera

import android.content.Context
import android.graphics.Matrix
import android.os.Build
import android.util.AttributeSet
import android.view.TextureView
import android.widget.FrameLayout
import androidx.annotation.FloatRange
import androidx.annotation.RequiresApi
import com.andova.glcamera.device.CameraBuilder
import com.andova.glcamera.device.ASPECT_RATIO_4_3
import com.andova.glcamera.device.AspectRatio
import com.andova.glcamera.texture.*
import kotlin.math.min

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
open class GlCamera2View(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
    private var param = Param()
    private var useCamera2 = true
    private var mAdjustViewBounds = false
    private var glTexView: CameraTextureView? = null
    private var mFrameHThread: GlFrameHThread? = null
    private var displayOrientation = 0
    private var mAsFirst = true

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.GlCamera2View)
        useCamera2 = when (a.getInt(R.styleable.GlCamera2View_camera, 0)) {
            1 -> false
            0 -> Build.VERSION.SDK_INT >= 21
            else -> Build.VERSION.SDK_INT >= 21
        }
        mAdjustViewBounds = a.getBoolean(R.styleable.GlCamera2View_android_adjustViewBounds, false)
        a.recycle()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        // TODO: 2021/8/5 quit thread handler
    }

    private fun detach() {
        removeAllViews()
        onDetached()
    }

    private fun attach() {
        if (glTexView == null) {
            mAsFirst = true
            glTexView = if (useCamera2) ACamera2TextureView(context, null) else ACamera1TextureView(context, null).apply { setDisplayOrientation(displayOrientation) }
        }
        addView(glTexView, -1, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
        onAttached()
        return
        attachViewToParent(glTexView, -1, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
    }

    /**
     * 该方法最好率先调用
     */
    private fun useBufferRender(callback: BufferFrameCallback?, builder: CameraBuilder = CameraBuilder.build()): GlCamera2View {
        glTexView?.useBufferRender(callback, builder) ?: apply {
            param.bufferCallback = callback
            param.builder = builder
        }
        return this
    }

    /**
     * 该方法最好率先调用
     */
    fun useTextureRender(callback: TextureFrameCallback?, builder: CameraBuilder = CameraBuilder.build()): GlCamera2View {
        glTexView?.useTextureRender(callback, builder) ?: apply {
            param.textureCallback = callback
            param.builder = builder
        }
        return this
    }

    @Deprecated("some bug")
    private fun mirror(mirrorWidth: Boolean, mirrorHeight: Boolean): GlCamera2View {
        glTexView?.flipScale(if (mirrorWidth) -1f else 1f, if (mirrorHeight) -1f else 1f) ?: apply {
            param.mirrorWidth = mirrorWidth
            param.mirrorHeight = mirrorHeight
        }
        return this
    }

    /**
     * @see android.hardware.Camera#displayOrientation
     */
    fun displayOrientation(degree: Int): GlCamera2View {
        displayOrientation = degree
        return this
    }

    fun rotate(angle: Int): GlCamera2View {
        val radian = when (angle) {
            0 -> 0.0
            90 -> Math.PI / 2
            180 -> Math.PI
            270 -> Math.PI / 2 * 3
            else -> 0.0
        }
        glTexView?.rotateRadian(radian) ?: apply {
            param.radian = radian
        }
        return this
    }

    fun lazyCallback(sleepMillisecond: Long): GlCamera2View {
        glTexView?.lazyCallback(sleepMillisecond) ?: apply {
            param.sleepMillisecond = sleepMillisecond
        }
        return this
    }

    fun resume(): GlCamera2View {
        glTexView?.resume() ?: apply {
            param.pause = false
        }
        return this
    }

    fun pause(clearColor: Boolean = true): GlCamera2View {
        glTexView?.pause(clearColor) ?: apply {
            param.pause = true
        }
        return this
    }

    fun action(@FrameAction command: Int): GlCamera2View {
        glTexView?.action(command) ?: apply {
            param.action = command
        }
        return this
    }

    fun handler(handler: GlFrameHandler?): GlCamera2View {
        param.handler = handler
        return this
    }

    fun x(@FloatRange(from = 0.1, to = 2.0) x: Float): GlCamera2View {
        glTexView?.x(x) ?: apply {
            param.x = x
        }
        return this
    }

    fun y(@FloatRange(from = 0.1, to = 2.0) y: Float): GlCamera2View {
        glTexView?.y(y) ?: apply {
            param.y = y
        }
        return this
    }

    fun start(delayMillis: Long = 0L, format: Int = DEFAULT_COLOR_FORMAT) {
        attach()
        glTexView ?: throw NullPointerException("CameraTextureView should not be null")
        mFrameHThread = GlFrameHThread.get().apply {
            tryStart()
            setHandler(param.handler)
        }
        if (!mAsFirst) {
            glTexView!!.start(delayMillis, format)
            return
        }
        glTexView!!.apply {
            if (param.bufferCallback != null) useBufferRender(param.bufferCallback, param.builder) else useTextureRender(param.textureCallback, param.builder)
        }
            .flipScale(if (param.mirrorWidth) -1f else 1f, if (param.mirrorHeight) -1f else 1f)
            .rotateRadian(param.radian)
            .lazyCallback(param.sleepMillisecond)
            .apply { if (param.pause) pause() }
            .action(param.action)
            .handler(mFrameHThread)
            .x(param.x)
            .y(param.y)
            .start(delayMillis, format)
        mAsFirst = false
    }

    fun stop() {
        GlFrameHThread.get().tryQuit()
        mAsFirst = true
        glTexView?.stop()
        detach()
    }

    open fun onAttached() {}
    open fun onDetached() {}

    /**
     * Configures the transform matrix for TextureView
     */
    private fun configureTransform(angle: Int, width: Float, height: Float, textureView: TextureView) {
        val matrix = Matrix()
        when {
            angle == 180 -> matrix.postRotate(180f, width / 2f, height / 2f)
            angle % 180 == 90 -> {
                // Rotate the camera preview when the screen is landscape.
                matrix.setPolyToPoly(
                    floatArrayOf(
                        0f, 0f,  // top left
                        width, 0f,  // top right
                        0f, height,  // bottom left
                        width, height
                    ), 0,
                    if (angle == 90) floatArrayOf(
                        0f, height,  // top left
                        0f, 0f,  // top right
                        width, height,  // bottom left
                        width, 0f
                    )
                    else floatArrayOf(
                        width, 0f,  // top left
                        width, height,  // top right
                        0f, 0f,  // bottom left
                        0f, height
                    ),
                    0, 4
                )
            }
        }
        textureView.setTransform(matrix)
    }

    fun getCamera() = glTexView?.getCamera()
    private fun isLandscape(): Boolean = param.radian % (Math.PI / 2) == 0.0
    private fun isPortrait(): Boolean = param.radian % Math.PI == 0.0
    private fun isCameraOpened() = glTexView?.getCamera()?.isCameraOpened() ?: false
    private fun getAspectRatio(): AspectRatio = glTexView?.getCamera()?.getAspectRatio() ?: ASPECT_RATIO_4_3
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (isInEditMode) return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (mAdjustViewBounds) { // Handle android:adjustViewBounds
            if (!isCameraOpened()) return super.onMeasure(widthMeasureSpec, heightMeasureSpec)// reserveRequestLayoutOnOpen
            val widthMode = MeasureSpec.getMode(widthMeasureSpec)
            val heightMode = MeasureSpec.getMode(heightMeasureSpec)
            if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
                val ratio: AspectRatio = getAspectRatio()
                var height = (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat()).toInt()
                if (heightMode == MeasureSpec.AT_MOST) {
                    height = min(height, MeasureSpec.getSize(heightMeasureSpec))
                }
                super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
            } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
                val ratio: AspectRatio = getAspectRatio()
                var width = (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat()).toInt()
                if (widthMode == MeasureSpec.AT_MOST) {
                    width = min(width, MeasureSpec.getSize(widthMeasureSpec))
                }
                super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightMeasureSpec)
            } else super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        } else super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // Measure the TextureView
        val width = measuredWidth
        val height = measuredHeight
        var ratio: AspectRatio = getAspectRatio()
        if (isPortrait()) ratio = ratio.inverse()
        if (height < width * ratio.y / ratio.x) {
            glTexView?.measure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(width * ratio.y / ratio.x, MeasureSpec.EXACTLY)
            )
        } else {
            glTexView?.measure(
                MeasureSpec.makeMeasureSpec(height * ratio.x / ratio.y, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
            )
        }
    }

    private class Param {
        @FrameAction
        var action: Int = FRAME_ACTION_NORMAL
        var radian = 0.0
        var pause = false
        var x: Float = 1f
        var y: Float = 1f
        var sleepMillisecond: Long = 0L
        var mirrorWidth: Boolean = false
        var mirrorHeight: Boolean = false
        var handler: GlFrameHandler? = null
        var bufferCallback: BufferFrameCallback? = null
        var textureCallback: TextureFrameCallback? = null
        var builder: CameraBuilder = CameraBuilder.build()
    }
}